package com.mebs.edi.parser;

import java.io.InputStream;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

import org.apache.log4j.Logger;

import com.mebs.edi.datalayer.DaoFactory;
import com.mebs.edi.model.edi.EdiIsa;
import com.mebs.edi.model.edi.EdiSt;
import com.mebs.edi.model.edi.e211.Edi211At;
import com.mebs.edi.model.edi.e211.Edi211Bol;
import com.mebs.edi.model.edi.e211.Edi211Gn;
import com.mebs.edi.model.edi.e211.Edi211Nn;
import com.mebs.edi.model.edi.e211.Edi211St;
import com.mebs.model.Address;
import com.mebs.model.AuditInfo;
import com.mebs.model.Company;
import com.mebs.model.Country;
import com.mebs.model.User;
import com.mebs.model.Vendor;
import com.mebs.model.WeightUnit;
import com.mebs.model.inventory.AbstractOrder;
import com.mebs.model.inventory.OrderItem;
import com.mebs.model.inventory.PurchaseOrder;
import com.mebs.model.logistics.Air;
import com.mebs.model.logistics.Consignee;
import com.mebs.model.logistics.Notify;
import com.mebs.model.logistics.Ocean;
import com.mebs.model.logistics.Transportation;
import com.mebs.model.logistics.Truck;

/**
 * 
 * This class uses EDI model classes to populate
 * corresponding MEBS model classes
 * 
 * @author nit
 * @version 1.0
 * 
 */
public class EdiToMebsMapper {
	
	private static final Logger LOG = Logger.getLogger(EdiToMebsMapper.class);
	
	public static final String AIR_TRANSPORTATION_TYPE = "AI";
	public static final String EXPEDITED_AIR_TRANSPORTATION_TYPE = "EA";
	public static final String OCEAN_TRANSPORTATION_TYPE = "SE";
	public static final String TRUCK_TRANSPORTATION_TYPE = "LN";
	
	// Default company, vendor and user associated with Transportation created from 211 EDI files
	private static final Long EDI_COMPANY_ID;
	private static final Long EDI_VENDOR_ID;
	private static final Long EDI_USER_ID;
	
	private static final String PARSER_MODULE_PROPERTIES = "parser.properties";
	
	private static final String PROPERTY_EDI_COMPANY_ID = "edi.company.id";
	private static final String PROPERTY_EDI_VENDOR_ID = "edi.vendor.id";
	private static final String PROPERTY_EDI_USER_ID = "edi.user.id";
	
	static {
		Properties parserProperties = new Properties();
		
		LOG.debug("getting parser module properties: " + PARSER_MODULE_PROPERTIES);
        InputStream is = EdiToMebsMapper.class.getResourceAsStream("/" + PARSER_MODULE_PROPERTIES);
        try {
        	parserProperties.load(is);
            is.close();
            is = null;
        } catch (Exception e) {
        	LOG.fatal(PARSER_MODULE_PROPERTIES + " could not be located in classpath", e);
        }
        
        EDI_COMPANY_ID = Long.parseLong(parserProperties.getProperty(PROPERTY_EDI_COMPANY_ID));
        EDI_VENDOR_ID = Long.parseLong(parserProperties.getProperty(PROPERTY_EDI_VENDOR_ID));
        EDI_USER_ID = Long.parseLong(parserProperties.getProperty(PROPERTY_EDI_USER_ID));
	}
	
	public EdiToMebsMapper() {
		LOG.debug("Instantiating EdiToMebsMapper");
	}
	
	/**
	 * 
	 * Populate MEBS model objects associated with EDI 211 object
	 * (fields mapping is based on "211 Implementation_Guide.pdf" and "UPS-211-Mapping.xls")
	 * 
	 * @param edi211
	 */
	public void populateMebsClasses(EdiIsa edi211) {
		LOG.debug("populating MEBS classes for: " + edi211);
		
		List<EdiSt> stSections = edi211.getGs().get(0).getStSections();
		for (EdiSt st : stSections) {
			if (st instanceof Edi211St) {
				Edi211St st211 = (Edi211St)st;
				Transportation t = extractTransportationFromSt(st211);
				st211.setTransportation(t);
			} else {
				continue;
			}
		}
	}
	
	/**
	 * 
	 * Populate MEBS model objects associated with EDI 211 object
	 * (fields mapping is based on "211 Implementation_Guide.pdf" and "UPS-211-Mapping.xls")
	 * 
	 * @param edi211
	 */
	public Transportation extractTransportationFromSt(Edi211St st) {
		LOG.debug("extracting transportation from: " + st);
		Transportation t;
		
		/*
		String transportationMode = st.getMs3().getMs304(); // Transportation Method/Type Code (Mode of shipment)
		if (AIR_TRANSPORTATION_TYPE.equalsIgnoreCase(transportationMode) || 
				EXPEDITED_AIR_TRANSPORTATION_TYPE.equalsIgnoreCase(transportationMode)) {
			t = new Air();
		} else if (TRUCK_TRANSPORTATION_TYPE.equalsIgnoreCase(transportationMode)) {
			t = new Truck();
		} else if (OCEAN_TRANSPORTATION_TYPE.equalsIgnoreCase(transportationMode)) {
			t = new Ocean();
		} else {
			throw new IllegalArgumentException(st + 
					" - Unsupported transportation mode: " + transportationMode +
					". Cannot proceed with mapping.");
		}
		*/
		// every EDI transportation should have type Air
		t = new Air();
		
		Edi211Bol bol = st.getBol();
		
		t.setSequenceNumber(bol.getBol03());		// shipment identification number
		t.setStartDate(bol.getBol04()); 			// "the date the carrier picked up the freight"
		t.setFinishDate(st.getG62_1().getG6202());  // planned ship date
		
		// it looks like in MEBS every transportation should be associated with a company
		// otherwise, the transportation will not be visible in UI.
		t.setCompany(getEdiCompany());
		
		List<Edi211Nn> nnSections = st.getNnSections();
		for (Edi211Nn nn : nnSections) {
			Address addr = extractAddressFromNn(nn);
			nn.setAddress(addr);
			
			String entityIdentifier = nn.getN101(); // Entity Identifier Code
			if ("PW".equalsIgnoreCase(entityIdentifier)) {
				// "PW" - Pickup Address?
				t.setOrigin(addr);
			} else if ("SD".equalsIgnoreCase(entityIdentifier)) {
				// "SD" - Shipment Destination?
				t.setDestination(addr);
				
				Consignee consignee = new Consignee();
				
				String contactName = extractContactNameFromNN(nn);
				consignee.setPointOfContact(contactName);
				
				try {
					Address consigneeAddr = addr.clone();
					consignee.setAddress(consigneeAddr);
				} catch (CloneNotSupportedException e) {
					LOG.error("Cannot clone address: " + addr, e);
					// TODO: probably should re-throw this?
				}
				
				t.setConsignee(consignee);
			} else if ("CN".equalsIgnoreCase(entityIdentifier)) {
				// "CN" - Contact Notify
				Notify notify = new Notify();
				notify.setAddress(addr);
				t.setNotify(notify);
			}
		}
		
		Map<String, AbstractOrder> orders = new HashMap<String, AbstractOrder>();
			
		List<Edi211At> atSections = st.getAtSections();
		for (Edi211At at : atSections) {
			PurchaseOrder po = extractPurchaseOrderFromAt(at);
			String orderNumber = po.getNumber();
			
			if (orders.containsKey(orderNumber)) {
				po = (PurchaseOrder)orders.get(orderNumber);
			} else {
				orders.put(orderNumber, po);
			}
			
			if (po.getItems() == null) {
				po.setItems(new HashSet<OrderItem>());
			}
			
			OrderItem oi = extractOrderItemFromAt(at);
			oi.setOrder(po);
			po.getItems().add(oi);
			
			//dateOfOrder is a mandatory field
			po.setDateOfOrder(bol.getBol04());	// "the date the carrier picked up the freight"?
			
			at.setOrderItem(oi);
			at.setPurchaseOrder(po);
		}
		
		Set<AbstractOrder> orderSet = new HashSet<AbstractOrder>();
		orderSet.addAll(orders.values());
		t.setOrders(orderSet);
		
		t.setAuditInfo(getFreshAuditInfo());
		
		// TODO: have no idea, what this field is used for, but it is required in MEBS
		t.setSequence(1); 

		return t;
	}
	
	private String extractContactNameFromNN(Edi211Nn nn) {
		String contactName = null;
		
		List<Edi211Gn> g61Sections = nn.getGn();
		if (g61Sections != null && !g61Sections.isEmpty()) {
			Edi211Gn g61 = g61Sections.get(0);
			contactName = g61.getG6102();
		}
		
		return contactName;
	}

	private Address extractAddressFromNn(Edi211Nn nn) {
		LOG.debug("extracting address from: " + nn);
		
		Address a = new Address();
		
		//a.setName(nn.getN101() + ", " + nn.getN102());	// entity identifier code + name
		a.setName(nn.getN102()); // only a name
		a.setAddress(nn.getN301());				// address info
		a.setAddress2(nn.getN302());			// address info
		a.setCity(nn.getN401());				// city name
		a.setState(nn.getN402());				// state
		a.setZip(nn.getN403());					// postal code/zip code

		String countryIso = nn.getN404();		// country
		Country country = null;
		try {
			country = DaoFactory.getInstance().getCountryDao().getCountryByIso(countryIso);
		} catch (Exception e) {
			country = new Country();
			country.setIso(countryIso);
			country.setName(countryIso);
			
			DaoFactory.getInstance().getGenericDao().save(country);
		}
		a.setCountry(country);
		
		for (Edi211Gn gn : nn.getGn()) {
			String contactName = gn.getG6101() + ", " + gn.getG6102();	// Contact function code + Name
			if ("TE".equalsIgnoreCase(gn.getG6103())) { 		// Communication Number Qualifier
				a.setPhoneNumber(contactName + ": " + gn.getG6104()); 	// Communication Number
			} else if ("EM".equalsIgnoreCase(gn.getG6103())) {
				a.setEmail(contactName + ": "  + gn.getG6104());
			} else {
				throw new IllegalArgumentException("Cannot map Communication Number Qualifier (G6103), unknown value");
			}
		}
		
		return a;
	}
	
	private OrderItem extractOrderItemFromAt(Edi211At at) {
		LOG.debug("extracting order item from: " + at);
		
		OrderItem oi = new OrderItem();
		
		oi.setLineNumber(at.getAt101());			// Lading Line Item Number
		oi.setCommodityDescription(at.getAt401());	// Lading Description
		
		// AT2: Bill of Lading Line Item Detail
		oi.setQuantityOrdered(at.getAt201());		// Lading Quantity
		
		Integer weightUnitType = WeightUnit.getTypeByAbbreviation(at.getAt204().toString()); // Weight Unit Code
		oi.setWeightUnit(weightUnitType);
		oi.setWeight(at.getAt205().doubleValue());	//weight
		
		return oi;
	}
	
	private PurchaseOrder extractPurchaseOrderFromAt(Edi211At at) {
		LOG.debug("extracting purchase order from: " + at);
		
		PurchaseOrder po = new PurchaseOrder();
		
		po.setNumber(at.getSpo01());	// Purchase Order Number
		// could not map the rest of the fields
		
		po.setAuditInfo(getFreshAuditInfo());
		po.setCompany(getEdiCompany());
		
		// vendor is a mandatory field
		po.setVendor(getEdiVendor());
		
		return po;
	}
	
	private AuditInfo getFreshAuditInfo() {
		// a user is required for saving AuditInfo
		User ediUser = DaoFactory.getInstance().getUserDao().getById(EDI_USER_ID);
		
		AuditInfo auditInfo = new AuditInfo();
		auditInfo.setAuthTraceDate(new Date());
		auditInfo.setAuthTraceUser(ediUser);
		auditInfo.setCreatedByDate(new Date());
		auditInfo.setCreatedByUser(ediUser);
		
		return auditInfo;
	}
	
	private Company getEdiCompany() {
		Company ediCompany = DaoFactory.getInstance().getCompanyDao().getById(EDI_COMPANY_ID);
		return ediCompany;
	}
	
	private Vendor getEdiVendor() {
		Vendor ediVendor = DaoFactory.getInstance().getVendorDao().getById(EDI_VENDOR_ID);
		return ediVendor;
	}
}
